﻿using System.Collections.Generic;
using System.Xml.Linq;
using DVK.SP2007;
using System.Linq;
using System.Text;

namespace DVK.SP2007.UniversalClient
{
    /// <summary>
    /// Puts the Dvk data xml together from different parts and manages the parts.
    /// <remarks>
    /// &lt;dhl:dokument xmlns:dhl="http://www.riik.ee/schemas/dhl"&gt;
    /// </remarks>
    /// </summary>
    public class DvkXml
    {
        /// <summary>
        /// Dvk message root namespace.
        /// Value: "http://www.riik.ee/schemas/dhl"
        /// </summary>
        public const string RootNamespace = "http://www.riik.ee/schemas/dhl";

        public static readonly XName ElementNameSignedDoc = XName.Get("SignedDoc", "http://www.sk.ee/DigiDoc/v1.3.0#");
        public static readonly XName ElementNameDvkDocument = XName.Get("dokument", RootNamespace);

        private DvkMetainfoXmlPart _metainfoMessagePart;
        private DvkMetaXmlPart _metaxmlMessagePart;
        private DvkTransportXmlPart _transportMessagePart;

        /// <summary>
        /// Gets the signed document as Xml string.
        /// </summary>
        /// <value>The signed doc XML.</value>
        public string SignedDocXml
        {
            get
            {
                //Remove everything from 0 .. <SignedDoc> element and from the end
                var signedDocument = new UTF8Encoding().GetString(SignedDocInBytes);
                var startElement = "<SignedDoc";
                var endElement = "</SignedDoc>";

                var signedDocStartLocation = signedDocument.IndexOf(startElement);
                var signedDocPartLength = (signedDocument.IndexOf(endElement) - signedDocument.IndexOf(startElement)) + endElement.Length;

                signedDocument = signedDocument.Substring(
                                signedDocStartLocation,
                                signedDocPartLength
                                );
                return signedDocument;
                //return new UTF8Encoding().GetString(SignedDocInBytes);
            }
        }

        private byte[] _signedDocInBytes;
        /// <summary>
        /// Gets or sets the signed document in bytes.
        /// </summary>
        /// <value>The signed doc in bytes.</value>
        public byte[] SignedDocInBytes
        {
            get
            {
                return _signedDocInBytes;
            }
            set
            {
                _signedDocInBytes = value;
            }
        }

        /// <summary>
        /// Gets the metainfo xml part.
        /// </summary>
        /// <value>The metainfo.</value>
        public DvkMetainfoXmlPart Metainfo
        {
            get
            {
                return _metainfoMessagePart;
            }
        }

        /// <summary>
        /// Gets the metaxml part.
        /// </summary>
        /// <value>The meta XML.</value>
        public DvkMetaXmlPart MetaXml
        {
            get
            {
                return _metaxmlMessagePart;
            }
        }

        /// <summary>
        /// Gets the transport xml part.
        /// </summary>
        /// <value>The transport.</value>
        public DvkTransportXmlPart Transport
        {
            get
            {
                return _transportMessagePart;
            }
        }

        //TODO: Ajalugu? (Author, Date)


        private XElement _dvkXml;

        /// <summary>
        /// Gets the element.
        /// </summary>
        /// <value>The element.</value>
        public XElement Element
        {
            get
            {
                return _dvkXml;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DvkXml"/> class.
        /// 
        /// Creates instance with required parts/elements - metainfo, metaxml and transport.
        /// </summary>
        public DvkXml()
        {
            _dvkXml = new XElement(ElementNameDvkDocument);
            
            _metainfoMessagePart = new DvkMetainfoXmlPart();
            _dvkXml.Add(_metainfoMessagePart.Element);

            _metaxmlMessagePart = new DvkMetaXmlPart();
            _dvkXml.Add(_metaxmlMessagePart.Element);

            _transportMessagePart = new DvkTransportXmlPart();
            _dvkXml.Add(_transportMessagePart.Element);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DvkXml"/> class.
        /// 
        /// Creates instance from string. Maps required elements to parts.
        /// </summary>
        /// <param name="dvkXml">The DVK XML.</param>
        public DvkXml(string dvkXml)
        {
            _dvkXml = XElement.Parse(dvkXml);
            _metainfoMessagePart = 
                new DvkMetainfoXmlPart(
                        GetXmlPart(DvkMetainfoXmlPart.ElementNameMetainfo)
                    );
            
            _metaxmlMessagePart =
                new DvkMetaXmlPart(
                        GetXmlPart(DvkMetaXmlPart.ElementNameMetaXml)
                    );
            _transportMessagePart = new DvkTransportXmlPart(
                    GetXmlPart(DvkTransportXmlPart.ElementNameTransport)
                );
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DvkXml"/> class.
        /// 
        /// Creates DvkXml from element and maps required elements to xml parts.
        /// </summary>
        /// <param name="element">The element.</param>
        public DvkXml(XElement element)
        {
            _dvkXml = element;
            _metainfoMessagePart =
                new DvkMetainfoXmlPart(
                        GetXmlPart(DvkMetainfoXmlPart.ElementNameMetainfo)
                    );

            _metaxmlMessagePart =
                new DvkMetaXmlPart(
                        GetXmlPart(DvkMetaXmlPart.ElementNameMetaXml)
                    );
            _transportMessagePart = new DvkTransportXmlPart(
                    GetXmlPart(DvkTransportXmlPart.ElementNameTransport)
                );
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DvkXml"/> class.
        /// 
        /// Creates instance with contents to the dokument element.
        /// They must contain metainfo, metaxml and transport element!
        /// </summary>
        /// <param name="content">The content.</param>
        public DvkXml(params object[] content)
        {
            _dvkXml = new XElement(
                    DvkXml.ElementNameDvkDocument,
                    content
                );
            _metainfoMessagePart =
                    new DvkMetainfoXmlPart(
                        GetXmlPart(DvkMetainfoXmlPart.ElementNameMetainfo)
                    );

            _metaxmlMessagePart =
                    new DvkMetaXmlPart(
                        GetXmlPart(DvkMetaXmlPart.ElementNameMetaXml)
                    );
            _transportMessagePart = new DvkTransportXmlPart(
                    GetXmlPart(DvkTransportXmlPart.ElementNameTransport)
                );
        }

        /// <summary>
        /// Gets xml part.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <returns></returns>
        public XElement GetXmlPart(XName element)
        {
            var result = _dvkXml.Element(element);
            if (result == null)
                throw new DvkException("Elementi (" + element.ToString()+ ") ei leitud DVK XML -st.");
            return result;
        }

        /// <summary>
        /// Allows substitution of one xml element with another in the tree.
        /// </summary>
        /// <param name="xml">The XML.</param>
        public void SetXml(string xml)
        {
            XElement element = XElement.Parse(xml);

            XElement existingElement = _dvkXml.Element(element.Name);
            if (existingElement != null)
                existingElement.Remove();
            _dvkXml.Add(element);
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// 
        /// Turns DvkXml into xml string (with signed document).
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            string tempXml = string.Empty;
            if (this.SignedDocInBytes != null)
            {
                _dvkXml.Add(new XElement(XName.Get("SignedDoc")));// "<SignedDoc xmlns=\"\" />"
                tempXml = _dvkXml.ToString();

                //Make sure the result is all in UTF8 encoding
                //var tempBytes = new UTF8Encoding().GetBytes(tempXml);
                //tempXml = new UTF8Encoding().GetString(tempBytes);

                //Add the SignedDoc part to the XML.
                tempXml = 
                    tempXml.Replace(
                            "<SignedDoc xmlns=\"\" />",
                            SignedDocXml//.Replace("\n", "\r\n") //Fix possible mixup with linux & windows line ends
                        );//Clean xml with signeddoc info
            }
            if (string.IsNullOrEmpty(tempXml))
            {
                tempXml = _dvkXml.ToString();
                //var tempBytes = new UTF8Encoding().GetBytes(tempXml);
                //tempXml = new UTF8Encoding().GetString(tempBytes);
            }
            
            return tempXml;
        }

        /// <summary>
        /// Gets the element value.
        /// </summary>
        /// <param name="parentElement">The parent element of the element.</param>
        /// <param name="element">The element whos value is wanted.</param>
        public string GetElementValue(XName parentElementName, XName elementName)
        {
            var parentElement = GetDescendantElement(Element, parentElementName);

            if (parentElement == null)
                return null;

            var element = GetDescendantElement(parentElement, elementName);

            if (element == null)
                return null;

            return element.Value;
        }

        /// <summary>
        /// Get decendant element by name with namespace and then without namespace.
        /// </summary>
        /// <param name="parentElement"></param>
        /// <param name="elementName"></param>
        /// <returns></returns>
        private XElement GetDescendantElement(XElement parentElement, XName elementName)
        {
            var elements = parentElement.Descendants(elementName);

            if (elements == null || elements.Count() == 0)
            {
                XName elementNameWithoutNamespace = XName.Get(elementName.LocalName);
                elements = parentElement.Descendants(elementNameWithoutNamespace);

                if (elements == null || elements.Count() == 0)
                {
                    return null;
                }
            }
            return elements.First();
        }

        /// <summary>
        /// Generic method to add element to parent element somewhere in the tree.
        /// </summary>
        /// <param name="parent">The parent.</param>
        /// <param name="child">The child.</param>
        /// <param name="elementValue">The element value.</param>
        public void AddElement(XName parent, XName child, object elementValue)
        {
            var parents = Element.Descendants(parent);

            if (parents == null || parents.Count() == 0)
                return;

            var mainElement = parents.First();
            var element = mainElement.Element(child);
            
            //if element exists, i need to delete it. We replace it with other.
            var newElement = new XElement(
                        child,
                        elementValue
                    );
            if (element != null)
            {
                element.ReplaceWith(newElement);
            }
            else
            {
                mainElement.Add(newElement);
            } 
        }
    }
}
